//
// Created by haoy on 2017/4/29.
//

#ifndef RTC_XSWITCH2_CONNECTION_HPP
#define RTC_XSWITCH2_CONNECTION_HPP

#include <algorithm>
#include <codecs.hpp>
#include <functional>
#include <list>
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <xm_log.h>
#include <xrtc_endpoint.h>
#include <xrtcp_util.h>

#include "Audio.hpp"
#include "config.hpp"
#include "clock.hpp"
#include "log.hpp"
#include "Http.hpp"
#include "RtpHeader.hpp"

class Candidate {
public:
    std::string sdp;
    std::string mid;
    int mlineIndex;
};

class Conn;

struct ConnectionEventHandler {
    void (*on_conn_init)(Conn*, bool ok, void* data);
    void (*on_conn_subscribe)(Conn*, bool ok, void* data);
    void (*on_conn_trickle)(Conn*, bool ok, void* data);
    void (*on_conn_terminate)(Conn*, bool ok, void* data);
    void (*on_conn_answer)(Conn*, bool ok, void* data);
    void (*on_conn_setup)(Conn*, int setup_time, void* data);
};

class Conn : private HttpClient {
    static constexpr size_t kMaxPacketSize = 1200;

public:
    static bool on_stream_data(rtc_endpoint_t rtc, void *context, int stream_type, rtc_codec_id_t codec, rtc_codec_id_t fec_codec, int data_type, int is_rtcp,
                              int is_rtx, unsigned char *buf, int len);

    static void on_stream_event(rtc_endpoint_t rtc, void * context, int event, void * event_arg1, int event_arg2);;

public:
    Conn(Context& ctx, int rtc_id, ConnectionEventHandler handler, void* data);

    virtual ~Conn();

    void set_pub_rtc(const char* rtc_id) {
        pub_rtc_id_ = rtc_id;
    }

    void init();

    void answer();

    void trickle();

    void terminate();

    std::string get_rtc_url() const;

    void subscribe();

    std::string& remote_rtc_id() { return _remote_rtc_id; }

    std::string& local_rtc_id() { return _local_rtc_id; }

    std::string remote_sdp() { return _remote_sdp; }

    std::list<Candidate>& remote_candidates() { return _remote_candidates; }

    void send_packets();

    void get_send_recv_bytes(uint64_t &send, uint64_t &recv);

    void get_send_recv_packets(uint64_t &send, uint64_t &recv);

    bool is_terminating() {
        return _terminating;
    }

    bool is_terminated() {
        return _terminated;
    }

    bool is_setup() {
        return _setup;
    }

    Metric& metric() {
        return metric_;
    }

private:
    void send_video_packet(bool is_key_frame, bool is_marker, int size, uint64_t ms);

    void sendDummyAudioPacket(uint64_t now);

    void sendAudioPacket(uint64_t now);

    void handle_setup_event();

    // todo: rtcp should be handled inside of rtc_endpoint, then report to us
    void handle_incoming_rtcp(uint8_t* buf, int size);

    void handle_incoming_rtp(uint8_t* buf, int size);

private:
    Context& ctx_;
    rtc_endpoint_t _endpoint;
    std::string _local_rtc_id;
    rtc_endpoint_callback_link_t _callback_handle;

    bool _resched_video = true;
    bool _resched_audio = true;

    uint32_t _video_frame_size;
    Clock::time_point _next_video_frame_time;
    Clock::time_point _last_video_key_frame_time;
    Clock::time_point _start_time;
    uint16_t _video_seq_number = 10000;

    Clock::time_point next_audio_frame_time_;
    uint16_t audio_seq_number_ = 10000;
    uint32_t audio_rtp_timestamp_ = 10000;
    uint32_t audio_packet_id_ = 0;

    std::string _remote_rtc_id;
    std::string _remote_sdp;
    std::list<Candidate> _remote_candidates;

    bool _setup = false;
    bool _terminating = false;
    bool _terminated = false;

    Audio* audio_ = nullptr;

    rtcp_parser_t rtcp_parser_;
    Clock::time_point next_stat_time_;
    unsigned char buf_[kMaxPacketSize + 16];

    ConnectionEventHandler handler_;
    void* handler_data_;

    int rtc_id_;

    Metric metric_;
    const char* pub_rtc_id_ = nullptr;
};

#endif //RTC_XSWITCH2_CONNECTION_HPP
